﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Navigation;
using MC.Phone.Ads.Provider;
using MC.Phone.Ads.Strategy;
using MC.Phone.Ads.Utils;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Net.NetworkInformation;
using Microsoft.Phone.Reactive;

namespace MC.Phone.Ads
{
    [TemplatePart(Name = "Content", Type = typeof (ContentPresenter))]
    [ContentProperty("AdProviders")]
    public class AdControl : ContentControl
    {
        public static readonly DependencyProperty AdProvidersProperty =
            DependencyProperty.Register("AdProviders", typeof (ProviderCollection), typeof (AdControl), null);

        public static readonly DependencyProperty AdProviderStrategyProperty =
            DependencyProperty.Register("AdProviderStrategy", typeof (IAdProviderStrategy), typeof (AdControl), null);

        public static readonly DependencyProperty FallbackAdProviderNameProperty =
            DependencyProperty.Register("FallbackAdProviderName", typeof (string), typeof (AdControl), null);

        public static readonly DependencyProperty IsTestProperty =
            DependencyProperty.Register("IsTest", typeof (bool?), typeof (AdControl), null);

        public static readonly DependencyProperty ProvideCountryProperty =
            DependencyProperty.Register("ProvideCountry", typeof (ProvideCountry), typeof (AdControl),
                                        new PropertyMetadata(ProvideCountry.FromCulture));

        public static readonly DependencyProperty CountryCodeProperty =
            DependencyProperty.Register("CountryCode", typeof (string), typeof (AdControl), null);

        public new static readonly DependencyProperty VisibilityProperty =
            DependencyProperty.Register("Visibility", typeof (Visibility), typeof (AdControl),
                                        new PropertyMetadata(VisibilityChanged));

        private WeakEventListener<AdControl, object, AdEventArgs> _adEngagedListener;
        private WeakEventListener<AdControl, object, AdErrorEventArgs> _adErrorListener;

        private IAdProvider _lastProvider;
        private WeakEventListener<AdControl, object, NavigatingCancelEventArgs> _navigatingListener;
        private bool _navigationBehaviorInitialized;
        private WeakEventListener<AdControl, object, AdEventArgs> _newAdListener;
        private WeakEventListener<AdControl, object, AdEventArgs> _noAdListener;
        private IDisposable _disposable;

        public AdControl()
        {
            AdProviders = new ProviderCollection();
            AdProviderStrategy = new DefaultAdProviderStrategy();
            Loaded += OnLoaded;
            Unloaded += OnUnloaded;
        }

        public new Visibility Visibility
        {
            get { return (Visibility) GetValue(VisibilityProperty); }
            set
            {
                SetValue(VisibilityProperty, value);
                base.Visibility = value;
            }
        }


        [Category("Ad")]
        public ProviderCollection AdProviders
        {
            get { return (ProviderCollection) GetValue(AdProvidersProperty); }
            set { SetValue(AdProvidersProperty, value); }
        }

        [Category("Ad")]
        public IAdProviderStrategy AdProviderStrategy
        {
            get { return (IAdProviderStrategy) GetValue(AdProviderStrategyProperty); }
            set { SetValue(AdProviderStrategyProperty, value); }
        }

        [Category("Ad")]
        public string FallbackAdProviderName
        {
            get { return (string) GetValue(FallbackAdProviderNameProperty); }
            set { SetValue(FallbackAdProviderNameProperty, value); }
        }

        [Category("Ad")]
        public bool? IsTest
        {
            get { return (bool?) GetValue(IsTestProperty); }
            set { SetValue(IsTestProperty, value); }
        }

        [Category("Ad")]
        public ProvideCountry ProvideCountry
        {
            get { return (ProvideCountry) GetValue(ProvideCountryProperty); }
            set { SetValue(ProvideCountryProperty, value); }
        }

        /// <summary>
        /// The two-letter ISO Standard Country Code.
        /// </summary>
        [Category("Ad")]
        public string CountryCode
        {
            get { return (string) GetValue(CountryCodeProperty); }
            set { SetValue(CountryCodeProperty, value); }
        }

        private static void VisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var control = d as AdControl;
            if (control != null)
                control.AdProviderStrategy.Paused =
                    !(control.Visibility).ToBool();
        }

        public PhoneApplicationPage FindParentPage(DependencyObject obj)
        {
            if (obj == null)
                return null;
            if (obj is PhoneApplicationPage)
                return obj as PhoneApplicationPage;
            return FindParentPage(VisualTreeHelper.GetParent(obj));
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            if (!DesignerProperties.IsInDesignTool)
            {
                if (!_navigationBehaviorInitialized)
                {
                    PhoneApplicationPage parent = FindParentPage(this);
                    if (parent != null)
                    {
                        _navigatingListener = new WeakEventListener<AdControl, object, NavigatingCancelEventArgs>(this)
                                                  {
                                                      OnEventAction =
                                                          (instance, source, args) => instance.NavigationBehavior(args),
                                                      OnDetachAction =
                                                          weak => parent.NavigationService.Navigating -= weak.OnEvent
                                                  };
                        parent.NavigationService.Navigating += _navigatingListener.OnEvent;
                    }
                    _navigationBehaviorInitialized = true;
                    ProvideAd();
                }
            }
            else
            {
                object o =
                    XamlReader.Load(
                        string.Format(
                            @"<Grid xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
                                                    xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
                                                    Width=""480""
                                                    Height=""80""
                                                    Background=""{{StaticResource PhoneBackgroundBrush}}"">
                                                    <Border BorderBrush=""{{StaticResource PhoneAccentBrush}}""
                                                            BorderThickness=""1"">
                                                        <Grid>
                                                            <TextBlock Text=""WP Unified Ad""
                                                                        Foreground=""{{StaticResource PhoneAccentBrush}}""
                                                                        Margin=""0""
                                                                        VerticalAlignment=""Center""
                                                                        HorizontalAlignment=""Center""
                                                                        FontSize=""35"" />
                                                            <TextBlock Text=""Designmode""
                                                                        Foreground=""{{StaticResource PhoneAccentBrush}}""
                                                                        Margin=""0""
                                                                        VerticalAlignment=""Top""
                                                                        HorizontalAlignment=""Right""
                                                                        FontSize=""14"" />
                                                            <TextBlock Text=""{0}""
                                                                        Foreground=""{{StaticResource PhoneAccentBrush}}""
                                                                        Margin=""0""
                                                                        VerticalAlignment=""Top""
                                                                        HorizontalAlignment=""Left""
                                                                        FontSize=""14"" />
                                                        </Grid>
                                                    </Border>
                                                </Grid>",
                            Name));
                Content = o;
            }
        }

        private void OnUnloaded(object sender, RoutedEventArgs e)
        {
            Content = null;
            _navigatingListener = _navigatingListener.DetachToNull();
            _navigationBehaviorInitialized = false;
            _adEngagedListener = _adEngagedListener.DetachToNull();
            _newAdListener = _newAdListener.DetachToNull();
            _noAdListener = _noAdListener.DetachToNull();
            _adErrorListener = _adErrorListener.DetachToNull();
        }


        /// <summary>
        /// If you manually want to request a new adprovider, you can call this code. In most situations you probably don't want to do this.
        /// Though if you want to request a new ad when something happens in your application this would be the method to call.
        /// </summary>
        public void RotateAdProvider()
        {
            if (_lastProvider != null)
                _lastProvider.FireNoAd();
        }

        private void ProvideAd()
        {
            
            IObservable<IAdProvider> adProvider = NextAdProvider();
            _disposable = adProvider.Subscribe(provider =>
                                                              {
                                                                  _adEngagedListener = _adEngagedListener.DetachToNull();
                                                                  _newAdListener = _newAdListener.DetachToNull();
                                                                  _noAdListener = _noAdListener.DetachToNull();
                                                                  _adErrorListener = _adErrorListener.DetachToNull();
                                                                  if (_lastProvider != null)
                                                                      _lastProvider.Clean();

                                                                  if (provider != null)
                                                                  {
                                                                      _adEngagedListener = new WeakEventListener<AdControl, object, AdEventArgs>(this)
                                                                                               {
                                                                                                   OnEventAction = (instance, source, args) => instance.OnAdEngaged(args), OnDetachAction = weak => provider.AdEngaged -= weak.OnEvent
                                                                                               };
                                                                      provider.AdEngaged += _adEngagedListener.OnEvent;
                                                                      _newAdListener = new WeakEventListener<AdControl, object, AdEventArgs>(this)
                                                                                           {
                                                                                               OnEventAction = (instance, source, args) => instance.OnNewAd(args), OnDetachAction = weak => provider.NewAd -= weak.OnEvent
                                                                                           };
                                                                      provider.NewAd += _newAdListener.OnEvent;

                                                                      _noAdListener = new WeakEventListener<AdControl, object, AdEventArgs>(this)
                                                                                          {
                                                                                              OnEventAction = (instance, source, args) => instance.OnNoAd(args), OnDetachAction = weak => provider.NoAd -= weak.OnEvent
                                                                                          };
                                                                      provider.NoAd += _noAdListener.OnEvent;
                                                                      _adErrorListener = new WeakEventListener<AdControl, object, AdErrorEventArgs>(this)
                                                                                             {
                                                                                                 OnEventAction = (instance, source, args) => instance.OnAdError(args), OnDetachAction = weak => provider.AdError -= weak.OnEvent
                                                                                             };
                                                                      provider.AdError += _adErrorListener.OnEvent;

                                                                      _lastProvider = provider;
                                                                      Content = provider.ProvideAd();
                                                                  }
                                                                  else
                                                                  {
                                                                      Content = null;
                                                                  }
                                                              });
        }

        private void NavigationBehavior(NavigatingCancelEventArgs e)
        {
            PhoneApplicationPage page = FindParentPage(this);
            if (page == null)
                return;
            if (page.NavigationService == null)
                return;
            if (page.NavigationService.Source != e.Uri)
            {
                if (_disposable!=null)
                {
                    _disposable.Dispose();
                }
                if(AdProviderStrategy!=null)
                {
                    AdProviderStrategy.Paused = true;
                }
                if (_lastProvider != null)
                {
                    _lastProvider.Clean();
                }
                Content = null;
                _lastProvider = null;
            }
            else if (page.NavigationService.Source == e.Uri)
                ProvideAd();
        }


        private void OnAdError(AdErrorEventArgs e)
        {
            if (AdProviderStrategy.CurrentAdProvider != null &&
                e.AdProviderName == AdProviderStrategy.CurrentAdProvider.Name && AdError != null)
            {
                AdError(this, e);
            }
        }

        private void OnNoAd(AdEventArgs e)
        {
            if (AdProviderStrategy.CurrentAdProvider != null &&
                e.AdProviderName == AdProviderStrategy.CurrentAdProvider.Name && NoAd != null)
            {
                NoAd(this, new AdEventArgs {AdProviderName = e.AdProviderName});
            }
        }

        private void OnNewAd(AdEventArgs e)
        {
            if (AdProviderStrategy.CurrentAdProvider != null &&
                e.AdProviderName == AdProviderStrategy.CurrentAdProvider.Name && NewAd != null)
            {
                NewAd(this, new AdEventArgs {AdProviderName = e.AdProviderName});
            }
        }

        private void OnAdEngaged(AdEventArgs e)
        {
            if (AdProviderStrategy.CurrentAdProvider != null &&
                e.AdProviderName == AdProviderStrategy.CurrentAdProvider.Name && AdEngaged != null)
            {
                AdEngaged(this, new AdEventArgs {AdProviderName = e.AdProviderName});
            }
        }


        [Category("Ad")]
        public event EventHandler<AdEventArgs> NewAd;

        [Category("Ad")]
        public event EventHandler<AdEventArgs> NoAd;

        [Category("Ad")]
        public event EventHandler<AdEventArgs> AdEngaged;

        [Category("Ad")]
        public event EventHandler<AdErrorEventArgs> AdError;

        public IObservable<AdEventArgs> ObserveNewAd()
        {
            return Observable.FromEvent<AdEventArgs>(this, "NewAd").Select(e => e.EventArgs);
        }

        public IObservable<AdEventArgs> ObserveNoAd()
        {
            return Observable.FromEvent<AdEventArgs>(this, "NoAd").Select(e => e.EventArgs);
        }

        public IObservable<AdEventArgs> ObserveAdEngaged()
        {
            return Observable.FromEvent<AdEventArgs>(this, "AdEngaged").Select(e => e.EventArgs);
        }

        public IObservable<IAdProvider> NextAdProvider()
        {
            Debug.Assert(AdProviderStrategy != null, "Please provide an AdProviderStrategy.");
            Debug.Assert(AdProviders.Any(), "Please add at least one AdProvider.");
            Debug.Assert(!AdProviders.GroupBy(ap => ap.Name).Any(g => g.Count() > 2),
                         "Please don't add the same AdProvider multiple times.");
            string countryCode = RetrieveCountryCode(ProvideCountry);
            foreach (IAdProvider adProvider in AdProviders)
            {
                if (IsTest.HasValue)
                {
                    adProvider.IsTest = IsTest.Value;
                }
                adProvider.CountryCode = countryCode;
            }
            AdProviderStrategy.AdProviders = AdProviders;
            if(string.IsNullOrEmpty(AdProviderStrategy.FallbackAdProviderName))
                AdProviderStrategy.FallbackAdProviderName = FallbackAdProviderName;
            AdProviderStrategy.CountryCode = countryCode;
            AdProviderStrategy.Paused = !Visibility.ToBool();
            return AdProviderStrategy.ProvideNext().SubscribeOn(Scheduler.ThreadPool).ObserveOnDispatcher();
        }

        public string RetrieveCountryCode(ProvideCountry provideCountry)
        {
            if (provideCountry == ProvideCountry.No)
                return string.Empty;
            if (provideCountry == ProvideCountry.Yes)
                return CountryCode;
            string countryName = CultureInfo.CurrentCulture.GetCountryCode();

            if (countryName.Length != 2)
            {
                Debug.WriteLine("Country CurrentCulture is not a uni-code code: {0}. Returned null", countryName);
                return string.Empty;
            }

            return countryName;
        }
    }
}